using System;
using System.Collections;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Resources;
using System.Web;
using System.Web.Services.Description;
using System.Web.Services.Discovery;
using System.Web.Services.Protocols;
using System.Xml;
using System.Xml.Schema;

namespace Thinktecture.Tools.Web.Services.Extensions
{
    public class ValidationExtension : SoapExtension
    {
        private ValidationEventHandler validateEventHandler;
        private XmlSchemaSet schemaCache;

        /// <summary>
        /// Creates a new <see cref="ValidationExtension"/> instance.
        /// </summary>
        public ValidationExtension()
        {
            this.validateEventHandler = new ValidationEventHandler(OnValidate);
            schemaCache = new XmlSchemaSet();
        }

        /// <summary>
        /// Gets the initializer.
        /// </summary>
        /// <param name="serviceType">Service type.</param>
        /// <returns></returns>
        public override object GetInitializer(Type serviceType)
        {
            try
            {
                // load SOAP 1.1 schema from assembly resource file
                LoadSchemaFromResourceFile("Thinktecture.Tools.Web.Services.Extensions.Validation.schemas", "soapenvelope");
                //GetInstrinsicSchemas(serviceType, HttpContext.Current.Request.RawUrl); 
                Uri uri = HttpContext.Current.Request.Url;
                string path = uri.AbsolutePath;
                int charIndex = path.LastIndexOf("/");
                string serviceAsmxFile = path.Substring(charIndex + 1, path.Length - charIndex - 1) + "_Wsdl";

                string wsdlLocation = ConfigurationManager.AppSettings[serviceAsmxFile];
                // BDS: Map the relative path to the abs path.
                wsdlLocation = HttpContext.Current.Server.MapPath(wsdlLocation);
                ServiceDescription sd = ServiceDescription.Read(wsdlLocation);

                LoadSchemasFromServiceDescription(wsdlLocation);
            }
            catch (Exception ex)
            {
                throw new MetadataLoadingException("An error occured while trying to load validation schemas.", ex);
            }

            return schemaCache;

            //return typeof(ValidationExtension);
        }

        /// <summary>
        /// Gets the initializer.
        /// </summary>
        /// <param name="methodInfo">Method info.</param>
        /// <param name="attribute">Attribute.</param>
        /// <returns></returns>
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            ValidationAttribute va = attribute as ValidationAttribute;

            try
            {
                if (va.Namespace != null)
                    schemaCache.Add(va.Namespace, HttpContext.Current.Server.MapPath(va.SchemaLocation));
                // load SOAP 1.1 schema from assembly resource file
                LoadSchemaFromResourceFile("Thinktecture.Tools.Web.Services.Extensions.Validation.schemas", "soapenvelope");
                //GetInstrinsicSchemas(methodInfo.DeclaringType, HttpContext.Current.Request.RawUrl); 

                // Uri uri = new Uri(HttpContext.Current.Request.RawUrl);
                Uri uri = HttpContext.Current.Request.Url;
                string path = uri.AbsolutePath;
                int charIndex = path.LastIndexOf("/");
                string serviceAsmxFile = path.Substring(charIndex + 1, path.Length - charIndex - 1);

                string wsdlLocation = ConfigurationManager.AppSettings[serviceAsmxFile];
                // BDS: Map the relative path to the abs path.
                wsdlLocation = HttpContext.Current.Server.MapPath(wsdlLocation);
                ServiceDescription sd = ServiceDescription.Read(wsdlLocation);

                LoadSchemasFromServiceDescription(wsdlLocation);
            }
            catch (Exception ex)
            {
                throw new MetadataLoadingException("An error occured while trying to load validation schemas.", ex);
            }

            return schemaCache;
        }

        /// <summary>
        /// Initializes the specified initializer.
        /// </summary>
        /// <param name="initializer">Initializer.</param>
        public override void Initialize(object initializer)
        {
            schemaCache = (XmlSchemaSet)initializer;
        }

        /// <summary>
        /// Loads the schema from resource file.
        /// </summary>
        /// <param name="root">Root.</param>
        /// <param name="resourceName">Name of the resource.</param>
        private void LoadSchemaFromResourceFile(string root, string resourceName)
        {
            ResourceManager resmgr = new ResourceManager(root, Assembly.Load(Assembly.GetExecutingAssembly().ToString()));
            string resSchema = resmgr.GetString(resourceName);
            StringReader sreader = new StringReader(resSchema);
            XmlTextReader xtr = new XmlTextReader(sreader);
            XmlSchema soapSchema = XmlSchema.Read(xtr, null);
            schemaCache.Add(soapSchema);
            xtr.Close();
        }

        /// <summary>
        /// Processs the message.
        /// </summary>
        /// <param name="message">Message.</param>
        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeDeserialize:
                    try
                    {
                        XmlReaderSettings settings = new XmlReaderSettings();
                        settings.ValidationType = ValidationType.Schema;
                        settings.Schemas.Add(this.schemaCache);
                        settings.ValidationEventHandler += this.validateEventHandler;
                        XmlReader vr = XmlReader.Create(new XmlTextReader(message.Stream),
                            settings);

                        while (vr.Read()) ;
                    }
                    finally
                    {
                        message.Stream.Position = 0;
                    }
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Ons the validate.
        /// </summary>
        /// <param name="source">Source.</param>
        /// <param name="args">Args.</param>
        private void OnValidate(object source, ValidationEventArgs args)
        {
            throw new SoapException(String.Format(CultureInfo.CurrentCulture, "Validation error: {0}", args.Exception.Message), SoapException.ClientFaultCode);
        }

        /// <summary>
        /// Gets the instrinsic schemas.
        /// </summary>
        /// <param name="type">Type.</param>
        /// <param name="url">URL.</param>
        private void GetInstrinsicSchemas(Type type, string url)
        {
            //ServiceDescriptionReflector sdr = new ServiceDescriptionReflector();
            //sdr.Reflect(type, url);

            //foreach (XmlSchema xsd in sdr.Schemas)
            //	schemaCache.Add(xsd);

        }

        /// <summary>
        /// Loads the schemas from service descriptions.
        /// </summary>
        /// <param name="wsdlLocation">Sd.</param>
        private void LoadSchemasFromServiceDescription(string wsdlLocation)
        {
            ArrayList tempCache = new ArrayList();
            DiscoveryClientProtocol dcp = new DiscoveryClientProtocol();
            dcp.AllowAutoRedirect = true;
            dcp.Credentials = CredentialCache.DefaultCredentials;

            dcp.DiscoverAny(wsdlLocation);
            dcp.ResolveAll();

            if (dcp.Documents.Count > 0)
            {
                foreach (object osd in dcp.Documents.Values)
                {
                    if (osd is XmlSchema)
                    {
                        if (((XmlSchema)osd).Includes.Count == 0)
                            schemaCache.Add((XmlSchema)osd);
                        else
                            tempCache.Add((XmlSchema)osd);
                    }
                }

                foreach (object schema in tempCache)
                {
                    schemaCache.Add((XmlSchema)schema);
                }
            }
        }
    }
}
